from pylons import config
import os
import os.path
import logging

import urlparse
import urllib2
from jsonobj import JsonObj
import md5
import thread
import Queue

#Package Dict has thisProperties:


class Package(JsonObj):
    pckgFile = 'case_package.json'
    testCaseId = 'testCaseId'
    name = 'name'
    fileUrl = 'fileUrl'
    expectMD5 = 'expectMD5'
    realMD5 = 'realMD5'
    tryTimes = 'tryTimes'
    status = 'status'
    
    STATUS_downloading = 'downloading'
    STATUS_download_ok = 'download_ok'
    STATUS_download_failed = 'download_failed'
    STATUS_verified_failed = 'verified_failed'
    STATUS_verified_ok = 'verified_ok'
    STATUS_extracted = 'extracted'
    STATUS_installed = 'installed'
    STATUS_package_existed = 'STATUS_package_existed'
    STATUS_package_existed = 'STATUS_install_failed'

    
    def increaseTryTimes(self, save=True):
        times = self.propDict.get(Package.tryTimes, 0)
        times += 1
        self.propDict[Package.tryTimes] = times
        if save == Ture:
            self.save()


class InstallerManager:
    logger = logging.getLogger(InstallerManager.__name__)
    lstInstalled = []
    #lstInstalling = []
    casesDir = config["dir.cases"]
    queueInstalling = Queue.Queue()
    lstThreads = []
    threadNum = 3
    
    def workerThreadMethod(self):
        while True:
            installer = self.queueInstalling.get()
            try:
                installer.perform()
            except Exception as e:
                self.logger.error("failed to execute installer perform method, %s" % e)
        
    
    def startThreads(self):
        for i in range(self.threadNum):
            try:
                lstThreads = thread.start_new_thread(self.workerThreadMethod(), None)
            except:
                pass
        
    
    def init(self):
        self.tempPath = os.path.join(self.casesDir,
                                     "temp")
        self.installedPath = os.path.join(self.casesDir,
                                          "installed")
        
        if os.path.isdir(self.tempPath) == False:
            os.makedirs(self.tempPath)
            
        if os.path.isabs(self.installedPath) == False:
            os.makedirs(self.installedPath)
            
        self.scanPackages()

    def recoverWorkingInstaller(self):
        logger.info("scanning packages")
        lstDirInstallingPacks = os.walk(self.tempPath)
        #lstDirInstalledPacks = os.walk(self.installedPath)
        
        for folder in lstDirInstallingPacks:
            absPath = os.path.join(self.tempPath, folder, Package.pckgFile)
            package = Package(absPath)
            package.load()
            installer = Installer(package, self)
            self.queueInstalling.put(installer)
            #self.lstInstalling.append(package)
        
    def listInstalledPackages(self):
        lstDirInstalledPacks = os.walk(self.installedPath)
        lstReturn = []
        for folder in lstDirInstallingPacks:
            absPath = os.path.join(self.tempPath, folder, Package.pckgFile)
            package = Package(absPath)
            package.load()
            lstReturn.append(package.propDict)
            
        return lstReturn
    

class SuccessfulFinishedException(Exception):
    pass

class InterupptRetryException(Exception):
    reason = 'unknown'
    def __init__(self, reason):
        self.reason = reason
        
    def __str__(self):
        return repr(self.reason)

class Installer:
    package = None
    installManager = None
    forceInstall = False
    resumeDownload = False
    bufSize = 40960
    sleepInterval = 0.2
    logger = logging.getLogger(Installer.__name__)
    retryTimes = 5
    
    def __init__(self, package, installerManager,
                 forceInstall=False, resumeDownload=False):
        
        self.package = package
        self.installManager = installerManager
        self.resumeDownload = resumeDownload
        self.forceInstall = forceInstall
        
        self.installPath = join(self.installManager.installedPath, 
                           self.package.getProp( Package.testCaseId))
        
        self.tempPath = os.path.join(self.installManager.tempPath,
                                  package.getProp(Package.testCaseId))
        
    def download(self):
        
        m = md5.new()
        r = urlparse.urlparse(self.package.getProp(Package.fileUrl))
        dstFolder = self.tempPath 
        
        if os.path.isdir(dstFolder) == False:
            os.makedirs(dstFolder)
        
        dstFile = os.path.join(dstFolder, r.path[r.path.rfind("/") + 1:])
        
        if os.path.exists(dstFile) == True:
            os.remove(dstFile)
        
        dstF = None
        srcF = None
        
        try:
            dstF = open(dstFile, 'wb')
            srcF = urllib2.urlopen(self.package.fileUrl)
            self.package.setProp(Package.status, Package.STATUS_downloading, save=True)  
            while True:
                buf = srcF.read(self.bufSize)
                if len(buf) == 0:
                    break
                
                dstF.write(buf)
                m.update(buf)
                time.sleep(self.sleepInterval)
            
            self.package.setProps({Package.status: Package.STATUS_download_ok,
                                   Package.realMD5: m.hexdigest()}, 
                                   save=True)
            
        except:
            self.package.setProp(Package.status, Package.STATUS_download_failed, save=True)  
            
        
        finally:
            if dstF != None:
                dstF.close()
            
            if srcF != None:
                srcF.close()
        
    def verifyMD5(self):
        expactMD5 = self.package.getProp(Package.expectMD5)
        realMD5 = self.package.getProp(Package.realMD5)
        if expactMD5 == realMD5:
            self.package.setProp(Package.status, Package.STATUS_verified_ok, save = True)
        else:
            self.package.setProp(Package.status, Package.STATUS_verified_failed, save = True)
        
        
    def install(self):
        if os.path.isfile(self.installPath):
            os.remove(self.installPath)
        
        if os.path.isdir(self.installPath):
            if self.forceInstall == False:
                self.package.setProp(Package.status, Package.STATUS_package_existed, save = True)
                return
            
            os.remove(self.installPath)
        
        try:
            os.rename(self.tempPath, self.installPath)
            self.package.setProp(Package.status, Package.STATUS_installed, save = True)
            self.logger.info("finished install: %s", self.installPath)
        except:
            self.package.setProp(Package.status, Package.STATUS_install_failed, save = True)
        
    
    def nextAction(self):
        status = self.package.getProp(Package.status)
        if status in [None, 
                  Package.STATUS_downloading, 
                  Package.STATUS_download_failed]:
            
            self.download()
            
        elif status == Package.STATUS_download_ok:
            self.verifyMD5()
        
        elif status == Package.STATUS_verified_ok:
            self.install()
        
        elif status == Package.STATUS_verified_failed:
            raise InterupptRetryException, Package.STATUS_verified_failed
            
        elif status == Package.STATUS_installed:
            raise SuccessfulFinishedException()
    
    def perform(self):
        try:
            for i in range(self.retryTimes):
                self.nextAction()
                
        except SuccessfulFinishedException:
            pass
        except InterupptRetryException:
            pass
        
    
    